package com.wtp.wallpaper.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wtp.wallpaper.common.constant.*;
import com.wtp.wallpaper.common.context.BaseContext;
import com.wtp.wallpaper.common.exception.PictureExpireException;
import com.wtp.wallpaper.es.document.PictureDocument;
import com.wtp.wallpaper.mapper.ClassifyMapper;
import com.wtp.wallpaper.pojo.dto.PictureDTO;
import com.wtp.wallpaper.pojo.entity.*;
import com.wtp.wallpaper.mapper.PictureMapper;
import com.wtp.wallpaper.pojo.query.PageQuery;
import com.wtp.wallpaper.pojo.vo.PictureVO;
import com.wtp.wallpaper.bloomfilter.BloomFilterService;
import com.wtp.wallpaper.rabbitmq.data.DataChangeEvent;
import com.wtp.wallpaper.rabbitmq.producer.PictureProducer;
import com.wtp.wallpaper.service.CollectService;
import com.wtp.wallpaper.service.PictureService;
import com.wtp.wallpaper.service.ScoreService;
import com.wtp.wallpaper.service.TabsService;
import com.wtp.wallpaper.utils.AliOssUtil;
import com.wtp.wallpaper.utils.PictureReduceUtil;
import com.wtp.wallpaper.utils.PojoConvertUtil;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Service
@Slf4j
@Transactional
public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture> implements PictureService {

    @Autowired
    private BloomFilterService bloomFilterService;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private AliOssUtil aliOssUtil;

    @Autowired
    private ClassifyMapper classifyMapper;

    @Autowired
    private TabsService tabsService;

    @Autowired
    private PictureProducer pictureProducer;

    @Autowired
    private CollectService collectService;

    @Autowired
    private ScoreService scoreService;


    //private static final int size = Runtime.getRuntime().availableProcessors();
    private static final int size = 6;
    private static final ExecutorService executorService = Executors.newFixedThreadPool(size);


    //private static final String RANDOM = "random_pic";
    @Override

    public List<PictureVO> randomWall() {

        String string = (String) redisTemplate.opsForValue().get(RedisConstant.RANDOM);
        List<PictureVO> pictures = null;
        if(StrUtil.isNotBlank(string)){
            pictures = JSON.parseArray(string, PictureVO.class);
            log.info("redis中获取:{}",pictures);
        }else{
            pictures = PojoConvertUtil.convert(getRandomWallPaper(list(), 9), PictureVO.class);
            log.info("查询获取:{}",pictures);
            //设置超时时间
            redisTemplate.opsForValue().set(RedisConstant.RANDOM,JSON.toJSONString(pictures),30, TimeUnit.MINUTES);
        }
        log.info("pictures:{}",pictures);
        return pictures;
    }

    @Override
    public Page<PictureVO> getPageList(PageQuery pageQuery) {

        Page<Picture> page = lambdaQuery().page(pageQuery.toMpPageDefaultSortByCreateTime());
        Page<PictureVO> pictureVOPage = new Page<>();


        return null;
    }

    @Override
    public List<PictureVO> getPageListByClassId(PageQuery pageQuery, long classId) {


        Page<Picture> page = lambdaQuery()
                .eq(Picture::getClassId, classId)
                .page(pageQuery.toMpPage("id", false));

        return PojoConvertUtil.convert(page.getRecords(), PictureVO.class);
    }

    @Override
    public Page<Picture> getPageListByPictureIds(Page<Picture> of, List<Long> pictureId) {

        return lambdaQuery()
                .in(Picture::getId, pictureId)
                .page(of);
    }

    @Override
    public Page<Picture> getSearchWallByKeyword(PageQuery pageQuery, String keyword) {
        return lambdaQuery()
                .like(Picture::getTabs, keyword)
                .or().like(Picture::getDescription, keyword)
                .page(pageQuery.toMpPage("id",false));
    }

    @Override
    public void addOne(PictureDTO pictureDTO) {
        //原图位置
        String f1 = PictureOssPathConstant.DEFAULT_TEMP + pictureDTO.getFileName();

        if(!new File(f1).exists()){
            throw new PictureExpireException(MessageConstant.PICTURE_EXPIRE);
        }

        //缩略图位置
        String f2 = PictureOssPathConstant.DEFAULT_TEMP_PROCESSED + pictureDTO.getFileName();

        String nickname = (String) BaseContext.getCurrentMap().get(JwtClaimsConstant.NICKNAME);
        List<String> tabs = new ArrayList<>();
        if(pictureDTO.getTabs() != null){
            tabs.addAll(Arrays.asList(pictureDTO.getTabs()));
        }
        Picture picture = Picture.builder()
                .classId(Long.valueOf(pictureDTO.getClassId()))
                .userId(Long.valueOf(BaseContext.getCurrentId()))
                .nickname(nickname)
                .description(pictureDTO.getDescription())
                .tabs(String.join(",", tabs))
                .smallpicurl(PictureOssPathConstant.PIC_SMALL_URL + "/" + pictureDTO.getFileName())
                .picurl(PictureOssPathConstant.PIC_URL + "/" + pictureDTO.getFileName())
                .fileMd5(pictureDTO.getFileMd5())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now()).build();

        log.info("上传图片:{}",picture);
        //信息存入数据库
        save(picture);
        bloomFilterService.addToBloomFilter(picture.getFileMd5());


        //图片转换上传完成后删除本地缓存
        CompletableFuture.runAsync(()->{
            try {
                //生成缩略图
                PictureReduceUtil.convert(
                        f1,
                        PictureOssPathConstant.DEFAULT_TEMP_PROCESSED);
                //上传图片到oss
                aliOssUtil.upload(f1, picture.getPicurl());
                aliOssUtil.upload(f2, picture.getSmallpicurl());

                Files.deleteIfExists(Path.of(f1));
                Files.deleteIfExists(Path.of(f2));
                //已上传后删除redis中的未上传记录
                redisTemplate.delete(RedisConstant.UPLOAD_IMAGE + pictureDTO.getFileName());

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        //上传到es索引库
        pictureProducer.sendPictureMessage(
                new DataChangeEvent(RabbitmqConstant.BATCH_CREATE,
                List.of(BeanUtil.copyProperties(picture, PictureDocument.class))));
    }

    @Override
    public void removePictureByIds(List<String> list) {
        List<Picture> pictureList = lambdaQuery()
                .in(Picture::getId, list)
                .list();
        //删除mysql中的记录
        removeByIds(list);
        QueryWrapper<Collect> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("pic_id",list);
        collectService.remove(queryWrapper);

        QueryWrapper<Score> scoreQueryWrapper = new QueryWrapper<>();
        queryWrapper.in("pic_id",list);
        scoreService.remove(scoreQueryWrapper);

        //删除oss中的存储
        aliOssUtil.remove(pictureList.stream()
                .map(Picture::getPicurl)
                .collect(Collectors.toList()));
        aliOssUtil.remove(pictureList.stream()
                .map(Picture::getSmallpicurl)
                .collect(Collectors.toList()));
        //删除es中的存储
        pictureProducer.sendPictureMessage(
                new DataChangeEvent(
                        RabbitmqConstant.BATCH_DELETE,
                        list));

    }

    @Override
    public void update(PictureDTO pictureDTO) {
        Picture picture = Picture.builder()
                .id(Long.valueOf(pictureDTO.getId()))
                .tabs(String.join(",", pictureDTO.getTabs()))
                .description(pictureDTO.getDescription())
                .classId(Long.valueOf(pictureDTO.getClassId()))
                .updateTime(LocalDateTime.now()).build();
        log.info("修改:{}",picture);
        saveOrUpdate(picture);

        //修改es
        pictureProducer.sendPictureMessage(
                new DataChangeEvent(
                        RabbitmqConstant.UPDATE,
                        BeanUtil.copyProperties(picture, PictureDocument.class)));
    }

    public Object[]  isRepeat(String path) throws IOException {
        return isRepeat(Files.newInputStream(Path.of(path)));
    }

    public Object[]  isRepeat(File file) throws IOException {
        return isRepeat(Files.newInputStream(file.toPath()));
    }

    @Override
    public Object[] isRepeat(InputStream inputStream) {

        //生成文件md5值
        String fileMd5 = PictureReduceUtil.calculateMD5(inputStream);
        boolean have = false;
        //布隆过滤器预判是否上传过该图片
        if(bloomFilterService.mightContain(fileMd5)){
            //二次校验数据库避免误判
            have = exists(new QueryWrapper<Picture>().eq("file_md5", fileMd5));
        }

        return new Object[]{fileMd5, have};
    }

    /**
     *
     * @param path 读取的输入路径
     */
    @Override
    public void addPictureBatch(Path path) {

        log.info("开始添加图片:{}",path.toString());
        String in = path.toString();
        File[] files = new File(in).listFiles();
        assert files != null;
        //第一级子目录全是分类名
        for (File file : files) {
            //分类id
            long id = 0L;

            Classify classify = classifyMapper.selectOne(new QueryWrapper<Classify>().eq("name", file.getName()));
            if(classify == null){
                classify = new Classify();
                classify.setName(file.getName());
                classify.setUpdateTime(LocalDateTime.now());
                classify.setCreateTime(LocalDateTime.now());
                //没有分类就添加
                classifyMapper.insert(classify);
                id = classify.getId();
            }else id = classify.getId();

            ArrayList<String> tabs = new ArrayList<>();
            //将该分类（文件夹名）下的所有图片添加到数据库 单个文件不添加
            ArrayList<Picture> pictureList = new ArrayList<>();
            //分类名也为标签名
            tabs.add(file.getName());
            add(    id,
                    file.listFiles(),
                    tabs,
                    pictureList
            );

            //批量添加图片到数据库
            saveBatch(pictureList);

            //批量添加到es
            pictureProducer.sendPictureMessage(
                    new DataChangeEvent(
                            RabbitmqConstant.BATCH_CREATE,
                            pictureList.stream()
                                    .map(picture -> BeanUtil.copyProperties(picture, PictureDocument.class))
                                    .toList()));
        }
        log.info("添加图片结束");

        // 关闭线程池
        //executorService.shutdown();
    }



    public void add(long classId, File[] files,List<String> tabs,List<Picture> pictureList){

        if(files == null) return;

        for (File file : files) {
            //当前分类下的所有子文件夹都为标签
            if(file.isDirectory()){

                //标签
                String name = file.getName();
                //标签不存在就添加
                boolean exists = tabsService.exists(new QueryWrapper<Tabs>().eq("tab_name", name));
                if(!exists){
                    tabsService.save(Tabs.builder()
                            .tabName(name)
                            .createTime(LocalDateTime.now())
                            .updateTime(LocalDateTime.now())
                            .build());
                }

                tabs.add(name);
                add(classId,file.listFiles(),tabs,pictureList);
                tabs.remove(name);
                continue;
            }

            //以下为文件操作
            //之前是否加入过该图片
            String fileMd5 = "";
            //原图位置
            String f1 = file.getPath();

            try {
                //没加入过返回null
                Object[] repeat = isRepeat(file);
                fileMd5 = (String) repeat[0];
                if(repeat[1].equals(true)){
                    Files.deleteIfExists(Path.of(f1));
                    log.info("文件重复删除成功: {}", f1);
                    continue;
                }
            } catch (IOException e) {
                log.error("删除重复文件失败: {}", f1);
                throw new RuntimeException(e);
            }

            convertAndUpload(file);

            bloomFilterService.addToBloomFilter(fileMd5);

            Picture picture = Picture.builder()
                    .classId(classId)
                    .userId(Long.valueOf(BaseContext.getCurrentId()))
                    .nickname((String)BaseContext.getCurrentMap().get(JwtClaimsConstant.NICKNAME))
                    .description("暂无")
                    .tabs(String.join(",", tabs))
                    .smallpicurl(PictureOssPathConstant.PIC_SMALL_URL + "/" + file.getName())
                    .picurl(PictureOssPathConstant.PIC_URL + "/" + file.getName())
                    .fileMd5(fileMd5)
                    .createTime(LocalDateTime.now())
                    .updateTime(LocalDateTime.now()).build();

            pictureList.add(picture);
        }
    }

    //将图片转换并上传到阿里云oss
    public void convertAndUpload(File file){
        //原图位置
        String f1 = file.getPath();
        //缩略图位置
        String f2 = PictureOssPathConstant.DEFAULT_TEMP_PROCESSED + file.getName();

        executorService.submit(
                () -> {
                    try {
                        //数据库中没有该图片 转换添加并上传
                        PictureReduceUtil.convert(file, PictureOssPathConstant.DEFAULT_TEMP_PROCESSED);
                        aliOssUtil.upload(f1,PictureOssPathConstant.PIC_URL + "/" + file.getName());
                        aliOssUtil.upload(f2,PictureOssPathConstant.PIC_SMALL_URL + "/" + file.getName());
                    }catch (Exception e){
                        log.error("图片处理或上传失败: {}", file.getName(), e);
                    } finally {
                        try {
                            Files.deleteIfExists(Path.of(f1));
                            log.info("删除临时文件成功: {}", f1);
                            Files.deleteIfExists(Path.of(f2));
                            log.info("删除临时文件成功: {}", f2);
                        } catch (IOException e) {
                            log.error("删除临时文件失败: {}", f1 + "/" + f2, e);
                        }

                    }
                }
        );

    }


    private List<Picture> getRandomWallPaper(List<Picture> list,int length){

        if(list.size() <= length) return list;

        List<Picture> result = new ArrayList<>();
        HashSet<Integer> set = new HashSet<>();
        int size = 0;
        while(size < length){
            int index = (int)(Math.random() * list.size());
            if(set.contains(index)){
                continue;
            }
            set.add(index);
            result.add(list.get(index));
            size++;
        }
        return result;
    }

}
